Go defer的使用场景及影响分析 您所在的位置:网站首页 go defer用法 Go defer的使用场景及影响分析

Go defer的使用场景及影响分析

#Go defer的使用场景及影响分析| 来源: 网络整理| 查看: 265

目录 前言1. defer特性1.1. 多个defer语句,按照先进后出的方式执行1.2. 延迟函数的参数在defer声明时就决定了1.3. 延迟函数对主函数的匿名返回值和具名返回值的操作影响?1.3.1 函数是如何返回的1.3.2 例1:主函数拥有匿名返回值的影响(不影响返回值)1.3.3 例2:主函数拥有匿名返回值的影响(不影响)1.3.4 例3:主函数拥有具名返回值的影响(影响返回值) 2. defer数据结构3. defer的创建和执行

前言

关键字 defer 用于注册延迟调用,每次 defer 都会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行即 defer 的调用直到return前才会被执行。 defer的用途:

关闭文件句柄锁资源释放数据库连接释放 1. defer特性 1.1. 多个defer语句,按照先进后出的方式执行

这个很好理解:后面的语句会依赖前面的资源,因此如果前面的资源先释放了,后面的语句就没法执行了。

每申请到一个用完需要释放的资源时,立即定义一个defer来释放资源是个很好的习惯。 例:

func main() { var test [3]struct{} for i := range test { defer fmt.Println(i) } } 输出: 2 1 0

defer 碰上闭包

也就是说函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成2,所以输出全都是2.

func main() { var test [3]struct{} for i := range test { defer func(){ fmt.Println(i) }() } } 输出: 2 2 2 1.2. 延迟函数的参数在defer声明时就决定了

如下代码所示:

func test() { i := 0 defer fmt.Println(i) i++ return }

defer语句中的 fmt.Println(i) 参数 i 值在 defer 出现时就已经确定下来,实际上是拷贝了一份,后面对变量 i 的修改不会影响 fmt.Println() 函数的执行,仍然打印 0

1.3. 延迟函数对主函数的匿名返回值和具名返回值的操作影响?

     主函数可能有返回值,返回值有没有名字没关系,defer所作用的函数,即延迟函数可能会影响到返回值。想知道是如果影响的只需要知道函数是如何返回的就够了

1.3.1 函数是如何返回的

没有加入 defer 的执行过程

func deferFuncReturn() (result int) { i := 1 return i }

deferFuncReturn()函数的return语句可以拆分为2行

result = i return

加入 defer 后的执行过程

func deferFuncReturn() (result int) { i := 1 defer func() { result++ }() return i }

因为defer的执行时机是在return之前所以加入defer后的执行过程如下:

result = i result++ return

所以上面函数实际返回i++值。

关于主函数有不同的返回方式,但返回机制就如上介绍所说,只要把return语句拆开都可以很好的理解,下面分别举例说明。

1.3.2 例1:主函数拥有匿名返回值的影响(不影响返回值)

一个主函数拥有一个匿名的返回值,返回时使用字面值,比如返回”1”、”2”、”Hello”这样的值,这种情况下defer语句是无法操作返回值的,如下所示:

func test() int { var i int defer func() { i++ }() return 1 }

上面的return语句,直接把1写入栈中作为返回值,延迟函数无法操作该返回值,所以就无法影响返回值。

1.3.3 例2:主函数拥有匿名返回值的影响(不影响)

一个主函数拥有一个匿名的返回值,返回使用本地或全局变量,这种情况下defer语句可以引用到返回值,但不会改变返回值

func test() int { var i int defer func() { i++ }() return i }

函数是匿名返回值的情况,它的执行过程过程如下(例1、例2均如此):

将 i 赋值给返回值(可以理解成Go自动创建了一个返回值retrurnValue,相当于执行retrurnValue = i)然后检查是否有defer,如果有则执行返回刚才创建的返回值(retrurnValue)

根据例1、例2可得结论: defer中的修改是对 i 执行的,而不是 returnValue,所以defer返回的依然是 returnValue

1.3.4 例3:主函数拥有具名返回值的影响(影响返回值)

主函声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。

func test() (i int) { defer func() { i++ }() return 0 }

以上代码执行过程:

i = 0 i++ return

函数真正返回前,在defer中对返回值做了+1操作,所以函数最终返回1。

根据例3可得结论: 由于返回值在方法定义时已经被定义,所以没有创建returnValue 的过程,i 就是 returnValue ,defer对于 i 的修改也会被直接返回

2. defer数据结构

Go1.7版本的defer的数据结构在包src/runtime/runtime2.go中 943 行有定义

type _defer struct { siz int32 // 参数和返回值共占多少字节 started bool // 是否已经执行 heap bool openDefer bool sp uintptr // 调用者栈指针 pc uintptr // 返回地址 fn *funcval // 函数地址(注册的函数地址) _panic *_panic link *_defer // 链向前一个defer结构体 fd unsafe.Pointer varp uintptr framepc uintptr }

defer 的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。

它与函数不同的一点是:它含有一个指针,可用于指向另一个 defer,每个goroutine 数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个 defer 时就将 defer 插入到单链表表头,每次执行 defer 时就从单链表表头取出一个 defer 执行

如下图所示: 在这里插入图片描述

3. defer的创建和执行

创建 defer 和 执行 defer方法的源代码在 src/runtime/panic.go 中

// 创建 func deferproc(siz int32, fn *funcval) {} func deferreturn() {}

deferproc():在声明 defer 处调用,将 defer 函数存入 goroutine 的链表中 deferreturn():在return前调用,将 defer 从 goroutine 链表中取出并执行

即在编译阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有